/*
    io_i2c.c - I2C Module for the io_extension (also called io_i2c interface driver)
	

	This file is used to activate the i2c Interface on the i/o extension
    controller!
	
	Author: Benedikt Niedermayr (2013)
	

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

	This code is also a result of my bachelor thesis.
	 
*/


#include "io_i2c.h"
#include "../io_communication_driver/io_com.h"

/* Driver specific Data */
#define MODULE_NAME "io_i2c"


char slave_addr[5]="00";
char reg_addr[5]="00";
char slave_port[2]="0";
char num_of_bytes[10]="";
int cnt = 1;



/** irq_bottom_half() - Interrupt functionality 
 * (But with no interrupt context -> instead make use of kthreats).
 * You can simply add your code here....
 */
static void irq_bottom_half(struct work_struct *work) 
{
	if(DEBUG == 1)
		printk("[%s] irq_bottom_half start...\n", MODULE_NAME);
}



/*
 * We do also support sysfs attributes!
 * change the I2C address via cat/echo commands
 */
static ssize_t read_i2c_slave_addr(struct device *dev, struct device_attribute *attr, char *buf)
{
	sprintf(buf, "%s\n", &slave_addr[0]);
	return strlen(buf)+1;
}


static ssize_t write_i2c_slave_addr(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
	strlcpy(&slave_addr[0], buf, count);
	return strlen(buf)+1;
}
static DEVICE_ATTR(slave_address, 0644, read_i2c_slave_addr, write_i2c_slave_addr);



/* Callback function for aio_read...
 * sync() kicks all completed iocbs and calls this function with
 * spezific iocb!
 */
static ssize_t async_read_retry( struct kiocb *iocb )
{
	int bytes_not_read = 0, bytes_to_read = 0;
	struct cmd *new_cmd =  (struct cmd *)iocb->private; //kmalloc(sizeof(*new_cmd), GFP_KERNEL);
	
	if(DEBUG == 1)
		printk( ".......aio_i2c_retry(%p)\n", iocb);

	if(new_cmd->corresp == NULL) {
		printk("Error while getting aligned answer payload!\n");
		return -EIO;	
	}

	/* We can easily use copy_to_user because we are in a
     * userpsace process kontext
     */
	bytes_to_read = min((int)new_cmd->corresp->answ_len, (int)iocb->ki_nbytes);
	
	if(DEBUG == 1)	
		printk("bytes_to_read:%d\n",bytes_to_read);

	/* we give back the return value from io_processor 
     * -> return requested payload 
     */
	bytes_not_read = copy_to_user(new_cmd->user_buf, new_cmd->corresp->answer_payload, bytes_to_read);
	list_del(&new_cmd->corresp->list);	
	kfree(new_cmd->corresp);
	list_del(&new_cmd->list);
	kfree(new_cmd);
	return bytes_to_read - bytes_not_read;
}

/* Callback function for aio_write...
 * sync() kicks all completed iocbs and calls this function with
 * spezific iocb!
 */
static ssize_t async_write_retry( struct kiocb *iocb )
{
	
	int res;
	struct cmd *new_cmd =  (struct cmd *)iocb->private; //kmalloc(sizeof(*new_cmd), GFP_KERNEL);

	if(DEBUG == 1)	
		printk( ".......aio_i2c_retry(%p)\n", iocb);
	
	if(new_cmd->corresp == NULL) {
		printk("Error while getting aligned return value!\n");
		return -EIO;	
	}

	/* We don't need to copy to user... This is a write callback function
     * We only return the spezific value (negative -> ERROR, positive -> bytes written)
     */
	
	sscanf((const char *)&new_cmd->corresp->answer_payload,"%d", &res);

	/* If something happens wrong, we inform the userspace via return value */
	if(new_cmd->corresp == NULL) {
		printk("error while getting aligned answer payload!\n");
		goto res_error_null;	
	}

	if(new_cmd->corresp->answ_len != 1)
		goto res_error;
	
	
	if(res < 0)
		goto res_error;



	list_del(&new_cmd->corresp->list);	
	kfree(new_cmd->corresp);
	list_del(&new_cmd->list);
	kfree(new_cmd);
	return new_cmd->payload_len;

res_error:
	printk("answer_payload =>%s", new_cmd->corresp->answer_payload);
res_error_null:
	list_del(&new_cmd->corresp->list);	
	kfree(new_cmd->corresp);
	list_del(&new_cmd->list);
	kfree(new_cmd);
	return -EAGAIN;	
}



/*
 * driver aio_read function handler
 * This function initializes the job which has to be done (add a new_cmd element to global cmd list, for later send to io processor)
 * 
 */
static ssize_t io_async_read(struct kiocb *iocb, char __user *buf, unsigned long count, loff_t offset)
{
	/* io_async_read:
	 * 1. Zuerst wird wieder ganz normal das Kommando erstellt und zur Anfrage List hinzugefügt
	 *
     * 2. dann wird dem kiobc die retry funktion zugeordnet, da der Auftrag unterwegs zum io-Prozessor ist.
	 *
	 * 3. Die Retry Funktion für read sieht etwas anders aus als für write!!!!!!
	 *		|-> Es sei denn, mann gibt immer den Payload der Antwort zurück!!
	 *
     * 4. Die Adresse des Userpsace buffers wird dem new_cmd element mitgegeben! (void *user_buf)
     *
	 * 6. Nun wird noch das new_cmd element an den iocbs angehängt, sodass das spezifische iocb von
     *    überall auf das zum iocbs gehörenden new_cmd zugreifen kann.
     *
	 * 7. iocbs counter++ (Sobald iocbs_counter > 0, so wird die Callback Funktion kick_iocb aufgerufen! Vorher nicht.)
     *
	 * 8. return -EIOCBRETRY (Da wir das kickerinterface verwenden)
	 */
	struct cmd *new_cmd;

	/*************  BUG ************
	 * We can now see, that the overgiven parameters(buf, count) both containing wrong values
     * for Userspace buffer address and bytes to read!!
	 * Instead I take Data from iocb->ki_nbytes and iocb->ki_buf
	 */

	if(DEBUG == 1) {	 
		printk( "driver_aioread(%p,%p,%d, bytes:%d )\n", iocb, buf, (int)count, iocb->ki_nbytes);
		printk("kiocb->ki_nbytes: %d\n", iocb->ki_nbytes);
		printk("kiocb->ki_buf: %p\n", iocb->ki_buf);
		printk("kiocb->ki_left: %d\n", iocb->ki_left);
	}

	/* new command list element */ 
	new_cmd = kmalloc(sizeof(*new_cmd), GFP_KERNEL);
	if (new_cmd == NULL) {
		return -ENOMEM;
	  }

	
	/* Prepare parameters */
	INIT_LIST_HEAD(&new_cmd->root.list);
 	sprintf(&num_of_bytes[0], "%d", iocb->ki_nbytes);


	/* Build parameters and add them to global cmd list 
     */
	add_param_to_list(1, &slave_port[0], new_cmd, strlen(&slave_port[0]));	
	add_param_to_list(2, &slave_addr[0], new_cmd, strlen(&slave_addr[0]));
	add_param_to_list(3, &reg_addr[0], new_cmd, strlen(&reg_addr[0]));
	add_param_to_list(4, &num_of_bytes[0], new_cmd, strlen(&num_of_bytes[0]));
	create_params_string(new_cmd);
	create_func_package(cnt, IO_I2C, DIR_IN, new_cmd);
	add_cmd_to_list(new_cmd, global_pointer);
	
	new_cmd->payload_len  = iocb->ki_nbytes;

	//down_read(&current->mm->mmap_sem);
	//result=get_user_pages( current, current->mm, (unsigned long)buf, 1, 0, 0, &new_cmd->page, NULL);
	//new_cmd->offset = (unsigned long)buf & ~PAGE_MASK;
	//up_read(&current->mm->mmap_sem);

	new_cmd->user_buf = (void *)iocb->ki_buf; 
	iocb->ki_retry = async_read_retry;
	iocb->private = (void *)new_cmd;
	
	

	mutex_lock(&global_pointer->iocb_lock);
		iocb_kick[global_pointer->iocb_cnt] = iocb;
		global_pointer->iocb_cnt++;		
	mutex_unlock(&global_pointer->iocb_lock);

	mutex_lock(&global_pointer->cnt_lock);
		cnt = (cnt + 1) % MAX_COMMAND_ID;
	mutex_unlock(&global_pointer->cnt_lock); 
	return -EIOCBRETRY;

}

/*
 * driver aio_write function handler
 * This function initializes the job which has to be done (add a new_cmd element to global cmd list, for later send to io processor)
 * 
 */
static ssize_t io_async_write(struct kiocb *iocb, char __user *buf, unsigned long count, loff_t offset)
{
	
	struct cmd *new_cmd;
	char *payload;
 
	if(DEBUG == 1)
		printk( "driver_aiowrite(%p,%p,%d, bytes:%d )\n", iocb, iocb->ki_buf, (int)count, iocb->ki_nbytes);

	/* new command list element */ 
	new_cmd = kmalloc(sizeof(*new_cmd), GFP_KERNEL);
	if (new_cmd == NULL) {
		return -ENOMEM;
	  }


	/* get payload data from userpsace */
	payload = new_char(MAX_DATA_LEN);
	if (copy_from_user(payload, iocb->ki_buf, iocb->ki_nbytes)) {
		goto data;
	}

	if(DEBUG == 1)
		printk("payload => %s\n", payload);

	


	/* Prepare parameters */
	INIT_LIST_HEAD(&new_cmd->root.list);
 	sprintf(&num_of_bytes[0], "%d", iocb->ki_nbytes);


	/* Build parameters and add them
     */
	add_param_to_list(1, &slave_port[0], new_cmd, strlen(&slave_port[0]));	
	add_param_to_list(2, &slave_addr[0], new_cmd, strlen(&slave_addr[0]));
	add_param_to_list(3, &reg_addr[0], new_cmd, strlen(&reg_addr[0]));
	add_param_to_list(4, &num_of_bytes[0], new_cmd, strlen(&num_of_bytes[0]));
	add_param_to_list(5, &payload[0], new_cmd, iocb->ki_nbytes);
	create_params_string(new_cmd);
	create_func_package(cnt, IO_I2C, DIR_OUT, new_cmd);
	add_cmd_to_list(new_cmd, global_pointer);
	

	new_cmd->payload_len  = iocb->ki_nbytes;
	//down_read(&current->mm->mmap_sem);
	//result=get_user_pages( current, current->mm, (unsigned long)buf, 1, 0, 0, &new_cmd->page, NULL);
	//new_cmd->offset = (unsigned long)buf & ~PAGE_MASK;
	//up_read(&current->mm->mmap_sem);

	new_cmd->user_buf = (void *)iocb->ki_buf; 
	iocb->ki_retry = async_write_retry;
	iocb->private = (void *)new_cmd;
	
	

	mutex_lock(&global_pointer->iocb_lock);
		iocb_kick[global_pointer->iocb_cnt] = iocb;
		global_pointer->iocb_cnt++;		
	mutex_unlock(&global_pointer->iocb_lock);

	mutex_lock(&global_pointer->cnt_lock);
		cnt = (cnt + 1) % MAX_COMMAND_ID;
	mutex_unlock(&global_pointer->cnt_lock); 
	return -EIOCBRETRY;


data:
	kfree(payload);	
	kfree(new_cmd); 
	printk("ERROR in i2c_write function\n");
	return -EFAULT;

}



/** 
 * io_i2c_read() - send a read command to io extension 
 * This function will send the command in synchronous mode.
 * Returns negative errno, or else number of bytes read from Kernelspace.
 */
static ssize_t io_i2c_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
{
	//char *tmp;
	//char *payload;
	int bytes_not_read = 0, bytes_to_read = 0;
	struct cmd *new_cmd;
	
	
	/* new command list element */ 
	new_cmd = kmalloc(sizeof(*new_cmd), GFP_KERNEL);
	if (new_cmd == NULL) {
		return -ENOMEM;
	  }


	/* Prepare parameters */
	INIT_LIST_HEAD(&new_cmd->root.list);
 	sprintf(&num_of_bytes[0], "%d", count);

	/* Build parameters and add them
     */
	add_param_to_list(1, &slave_port[0], new_cmd, strlen(&slave_port[0]));	
	add_param_to_list(2, &slave_addr[0], new_cmd, strlen(&slave_addr[0]));
	add_param_to_list(3, &reg_addr[0], new_cmd, strlen(&reg_addr[0]));
	add_param_to_list(4, &num_of_bytes[0], new_cmd, strlen(&num_of_bytes[0]));
	create_params_string(new_cmd);
	create_func_package(cnt, IO_I2C, DIR_IN, new_cmd);
	add_cmd_to_list(new_cmd, global_pointer);
	sync(global_pointer);

	/* If something happens wrong, we inform the userspace via return value */
	if(new_cmd->corresp == NULL) {
		printk("error while getting aligned answer payload!\n");
		goto free_list_cmd;	
	}
	

	/* we will now copy_to_user the answer_payload *************************************************************/
	bytes_to_read = min((int)new_cmd->corresp->answ_len, (int)count);
	bytes_not_read = copy_to_user(buf, new_cmd->corresp->answer_payload, bytes_to_read);
	
	if(DEBUG == 1) {
		printk("bytes_to_read => %d\n", bytes_to_read);
		printk("bytes_not_read => %d\n", bytes_not_read);	
	}

	//-> NOW FREE CMD LIST ELEMENT new_cmd
	list_del(&new_cmd->corresp->list);	
	kfree(new_cmd->corresp);
	list_del(&new_cmd->list);
	kfree(new_cmd);


	// Kommand ID Counter 0-98
	mutex_lock(&global_pointer->cnt_lock);
		cnt = (cnt + 1) % MAX_COMMAND_ID;
	mutex_unlock(&global_pointer->cnt_lock);	
	

	return bytes_to_read - bytes_not_read;

free_list_cmd:
	list_del(&new_cmd->list);
	kfree(new_cmd);
	printk("ERROR in i2c_write function\n");
	return -EFAULT;
}


/** 
 * io_i2c_write() - send a write command to io extension 
 * This function will send the command in synchronous mode.
 * Returns negative errno, or else number of bytes read from Kernelspace.
 */
static ssize_t io_i2c_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
{
	char *payload;
	struct cmd *new_cmd;
	
	/* new command list element */ 
	new_cmd = kmalloc(sizeof(*new_cmd), GFP_KERNEL);
	if (new_cmd == NULL) {
		return -ENOMEM;
	  }

	/* get payload data from userpsace */
	payload = new_char(MAX_DATA_LEN);
	if (copy_from_user(payload, buf, count)) {
		goto data;
		return -EFAULT;
	}

	/* Prepare parameters */
	INIT_LIST_HEAD(&new_cmd->root.list);
	sprintf(&num_of_bytes[0], "%d", count);
	
	/* Build parameters and add them
     */
	add_param_to_list(1, &slave_port[0], new_cmd, strlen(&slave_port[0]));	
	add_param_to_list(2, &slave_addr[0], new_cmd, strlen(&slave_addr[0]));
	add_param_to_list(3, &reg_addr[0], new_cmd, strlen(&reg_addr[0]));
	add_param_to_list(4, &num_of_bytes[0], new_cmd, strlen(&num_of_bytes[0]));
	add_param_to_list(5, &payload[0], new_cmd, count);
	create_params_string(new_cmd);
	create_func_package(cnt, IO_I2C, DIR_OUT, new_cmd);
	add_cmd_to_list(new_cmd, global_pointer);
	sync(global_pointer);

	/* If something happens wrong, we inform the userspace via return value */
	if(new_cmd->corresp == NULL) {
		printk("error while getting aligned answer payload!\n");
		goto free_list_cmd;	
	}

	/* In the write function we wont give back the answer_payload
     * Instead we will only check the answer payload  *******************************************************
     */
	if(DEBUG == 1) {
		if(SIMULATE == 1) {
			printk("Answer from io controller (for requested write) is %c\n", (new_cmd->corresp->answer_payload[0]-19));	// answer_payload[0] is C -> ascii C-19 = 0
		} else {
			printk("Answer from io controller (for requested write) is %c\n", new_cmd->corresp->answer_payload[0]);		
		}			
	}

	//-> NOW FREE CMD LIST ELEMENT new_cmd and corresp element!
	list_del(&new_cmd->corresp->list);	
	kfree(new_cmd->corresp);
	list_del(&new_cmd->list);
	kfree(new_cmd);


	// Kommand ID Counter 0-98
	mutex_lock(&global_pointer->cnt_lock);
		cnt = (cnt + 1) % MAX_COMMAND_ID;
	mutex_unlock(&global_pointer->cnt_lock);	
	
	kfree(payload);	
	return count;

free_list_cmd:
	list_del(&new_cmd->list);
	kfree(new_cmd);
data:
	//-> NOW FREE CMD LIST ELEMENT new_cmd
	kfree(payload);	 
	printk("ERROR in i2c_write function\n");
	return -EFAULT;
}





//syscall
static int io_i2c_open(struct inode *inode, struct file *file)
{
	return 0;
}

//syscall
static int io_i2c_release(struct inode *inode, struct file *file)
{
	

	return 0;
}

static const struct file_operations fops = {
	.owner		= THIS_MODULE,
	.read		= io_i2c_read,
	.write		= io_i2c_write,
	//.unlocked_ioctl	= io_i2c_ioctl,
	.open		= io_i2c_open,
	.release	= io_i2c_release,
	.aio_read	= io_async_read,
	.aio_write  = io_async_write,
};


static int __init io_i2c_init(void)
{	
	struct io_i2c *io_i2c_data;
	struct io_com *io_com_data;
	int ret;

	io_com_data = kmalloc(sizeof(*io_com_data), GFP_KERNEL);
	if(io_com_data == NULL)
		return -ENOMEM;
	
	/* connect the global structure */
	if(global_pointer == NULL) {
		printk("[%s] Global structure not found!\n", MODULE_NAME);
		goto free_io_com;	
	}

	io_com_data = global_pointer;
	io_i2c_data = kmalloc(sizeof(*io_i2c_data), GFP_KERNEL);

	if(io_i2c_data == NULL)
		return -ENOMEM;

	
	/* Get an object for later device creating -> new device model*/
	io_i2c_data->driver_number = MKDEV(MAJOR_IO_I2C, 0);
	if(register_chrdev(MAJOR_IO_I2C, "io_i2c_dev", &fops) == 0) {
	
		io_i2c_data->driver_dev = device_create(io_com_data->io_class, NULL, io_i2c_data->driver_number, NULL, "%s", "io_i2c");
		
		/* Create sysfs attribute */
		ret = device_create_file(io_i2c_data->driver_dev, &dev_attr_slave_address);	
		if(ret)
			return ret;
		/* connect io_i2c_data to the global structure */
		io_com_data->io_i2c_pointer = io_i2c_data;
	}

	/* Initialize work object function irq_bottom_half() */
	INIT_WORK(&i2c_bottom_half, &irq_bottom_half);	
	
	printk("[%s] driver loaded\n", MODULE_NAME);
	return 0;


free_io_com:
	kfree(io_com_data);
	return -EIO;

}

static void __exit io_i2c_exit(void)
{
	struct io_i2c *io_i2c_data;

	if(global_pointer->io_i2c_pointer == NULL)
		return;

	io_i2c_data = global_pointer->io_i2c_pointer;
	
	device_remove_file(io_i2c_data->driver_dev, &dev_attr_slave_address);
	//device_release_driver(io_i2c_data->driver_dev);
	device_destroy(global_pointer->io_class, io_i2c_data->driver_number);
	//cdev_del(io_i2c_data->driver_object);
	unregister_chrdev(MAJOR_IO_I2C, "io_i2c_dev");


	//dev_info(io_i2c_data->driver_dev, "[%s] driver unloaded", MODULE_NAME);
	printk("[%s] driver unloaded\n", MODULE_NAME);
}

module_init( io_i2c_init );
module_exit( io_i2c_exit );

MODULE_AUTHOR("Benedikt Niedermayr");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Additional i2c userspace interface driver for i/o-controller");
MODULE_SUPPORTED_DEVICE("none");

